package log

import (
	"fmt"

	"strings"

	"strconv"

	"github.com/pkg/errors"
	"go.uber.org/zap/zapcore"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

//
// Core
//

type M bson.M

type mongoWriter struct {
	session    *mgo.Session
	collection *mgo.Collection
}

func (self *mongoWriter) write(params bson.M) error {
	return self.collection.Insert(params)
}
func (self *mongoWriter) Debug(params bson.M) error {
	return self.write(params)
}

func (self *mongoWriter) Err(params bson.M) error {
	return self.write(params)
}

func (self *mongoWriter) Info(params bson.M) error {
	return self.write(params)
}

func (self *mongoWriter) Warning(params bson.M) error {
	return self.write(params)
}

func (self *mongoWriter) Crit(params bson.M) error {
	return self.write(params)
}

func NewHooker(mgoUrl, db, collection string) (*mongoWriter, error) {
	session, err := mgo.Dial(mgoUrl)
	if err != nil {
		return nil, err
	}
	return &mongoWriter{session: session, collection: session.DB(db).C(collection)}, nil
}

func NewHookerWithAuth(mgoUrl, db, collection, user, pass string) (*mongoWriter, error) {
	session, err := mgo.Dial(mgoUrl)
	if err != nil {
		return nil, err
	}

	if err := session.DB(db).Login(user, pass); err != nil {
		return nil, fmt.Errorf("Failed to login to mongodb: %v", err)
	}
	return &mongoWriter{session: session}, nil
}

func NewHookerWithAuthDb(mgoUrl, authdb, db, collection, user, pass string) (*mongoWriter, error) {
	session, err := mgo.Dial(mgoUrl)
	if err != nil {
		return nil, err
	}

	if err := session.DB(authdb).Login(user, pass); err != nil {
		return nil, fmt.Errorf("Failed to login to mongodb: %v", err)
	}
	return &mongoWriter{session: session}, nil
}

type Core struct {
	zapcore.LevelEnabler

	encoder zapcore.Encoder
	writer  *mongoWriter

	fields []zapcore.Field
}

func NewCore(enab zapcore.LevelEnabler, encoder zapcore.Encoder, writer *mongoWriter) *Core {
	return &Core{
		LevelEnabler: enab,
		encoder:      encoder,
		writer:       writer,
	}
}

func (core *Core) With(fields []zapcore.Field) zapcore.Core {
	// Clone core.
	clone := *core

	// Clone encoder.
	clone.encoder = core.encoder.Clone()

	// Clone and append fields.
	clone.fields = make([]zapcore.Field, len(core.fields)+len(fields))
	copy(clone.fields, core.fields)
	copy(clone.fields[len(core.fields):], fields)

	// Done.
	return &clone
}

func (core *Core) Check(entry zapcore.Entry, checked *zapcore.CheckedEntry) *zapcore.CheckedEntry {
	if core.Enabled(entry.Level) {
		return checked.AddCore(entry, core)
	}
	return checked
}

func (core *Core) Write(entry zapcore.Entry, fields []zapcore.Field) error {
	params := bson.M{}
	params["level"] = entry.Level.String()
	params["caller"] = strings.Join([]string{entry.Caller.File, strconv.Itoa(entry.Caller.Line)}, ":")
	params["stack"] = entry.Stack
	params["time"] = entry.Time
	params["message"] = entry.Message

	for _, field := range fields {
		params[field.Key] = field.Interface
	}
	// Write the message.
	switch entry.Level {
	case zapcore.DebugLevel:
		return core.writer.Debug(params)
	case zapcore.ErrorLevel:
		return core.writer.Err(params)

	case zapcore.InfoLevel:
		return core.writer.Info(params)

	case zapcore.WarnLevel:
		return core.writer.Warning(params)

	case zapcore.DPanicLevel:
		return core.writer.Crit(params)

	case zapcore.PanicLevel:
		return core.writer.Crit(params)

	case zapcore.FatalLevel:
		return core.writer.Crit(params)

	default:
		return errors.Errorf("unknown log level: %v", entry.Level)
	}
}

func (core *Core) Sync() error {
	return nil
}
